package com.jxb.demo.im.server;

import com.alibaba.fastjson.JSONObject;
import com.jxb.demo.im.MsgInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @desc 服务端心跳超时检测
 * 如果读超时5s，则发送心跳ping，连续超时3个T，则关闭链路，
 * 接收一次客户端请求，心跳超时则要清零
 */
public class IMServerHeartBeatHandler extends ChannelInboundHandlerAdapter {

    private static Logger logger = LoggerFactory.getLogger(IMServerHeartBeatHandler.class);

    //各个客户端连续超时次数
    private static Map<ChannelHandlerContext,Integer> overTimes = new ConcurrentHashMap<>();

    private Integer maxOverTimes = 3;//连续超时最大次数

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }

    /**
     * @desc 读取客户端信息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        MsgInfo msgInfo = (MsgInfo) msg;
        //如果是心跳超时检测
        if("heartBeat".equals(msgInfo.getMsgType())){
            logger.info("server recieve heartBeat:{}",JSONObject.toJSONString(msg));
            //如果收到心跳应答，服务端的心跳超时次数置0
            overTimes.remove(ctx);
        }else{
            ctx.fireChannelRead(msg);
        }
    }

    /**
     * @desc 读、写、读写超时，心跳检测链路可用
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent){
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            if(stateEvent.state() == IdleState.READER_IDLE){//读超时
                int beatTimes = overTimes.getOrDefault(ctx,0);
                //如果心跳超时次数小于最大超时次数，发送心跳，超时次数+1
                if(beatTimes < maxOverTimes){
                    MsgInfo beatMsg = new MsgInfo();
                    beatMsg.setMsg("ping");
                    ctx.channel().writeAndFlush(beatMsg);
                    overTimes.put(ctx,beatTimes+1);
                }else{//大于最大超时次数，链路关闭
                    overTimes.remove(ctx);
                    ctx.close();
                }
            }
        }
    }

    /**
     * @desc 异常捕获处理
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
}
